package com.jf.net.adapter

import android.util.Log
import com.jf.net.core.IError
import com.jf.net.core.IResponse
import com.jf.net.core.StatusValue
import com.jf.net.entity.ResponseError
import com.jf.net.entity.ResponseResult
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.retryWhen

fun <Target> Flow<ApiResponseData<Target>>.switchResponseResult(
    failureWithThrow: Boolean = true,
    stateCallback: ((state: Int, error: ResponseError?) -> Unit)? = null
): Flow<ResponseResult<Target?>> {
    return this.map { apiRsp->
        var state = StatusValue.SUCCESS
        val responseBody = apiRsp.data
        var error: ResponseError? = null
        if (apiRsp.code != "000000") {
            state = StatusValue.FAILURE
            error = ResponseError(
                errorCode = apiRsp.code,
                errorMsg = apiRsp.description,
                error = if(failureWithThrow) ResponseFailedException(apiRsp.code, apiRsp.description) else null
            )
            stateCallback?.invoke(state, error)
        }
        ResponseResult(state, responseBody, error)
    }
}

fun <Target> Flow<ApiResponseData<Target>>.withRetry(
    retrySetFun: (Int) -> Long
): Flow<ApiResponseData<Target>> {
    return this.retryWhen { cause, attempt ->
        val timeDelay = retrySetFun(attempt.toInt())
        if(timeDelay <= 0){
            false
        }else{
            delay(timeDelay)
            true
        }
    }
}

fun <Target> Flow<ResponseResult<Target>>.catch(
    failedCallback: ((state: Int, error: ResponseError?) -> Unit)? = null
): Flow<ResponseResult<Target>> {
    return this.catch { error->
        failedCallback?.invoke(StatusValue.FAILURE, ResponseError(
            errorCode = "504",
            errorMsg = error.message,
            error = error.cause
        ))
    }
}

fun <Target> Flow<ResponseResult<Target>>.switchData(
    failedCallback: ((state: Int, error: ResponseError?) -> Unit)? = null
): Flow<Target?> {
    return this.map { rsp->
        if(!rsp.isSuccess){
            failedCallback?.invoke(rsp.status, rsp.errors)
        }
        rsp.data
    }
}

val retryTimeMap = HashMap<Int, Int>()
const val DEFAULT_RETRY_TIME: Long = 5000*2
const val TAG = "JF_NET"
const val LOG_INFO = "request retried too many times..."

/***
 * 发送请求，若存在Exception则进行请求重试
 * @param type 发起请求类型，参考RequestType
 * @param retryLimit 请求失败重试次数（可选，默认为0），值小于等于0则无限制重试，值大于0则进行具体重试次数限制
 * @param doWork 结果回调函数
 * */
fun <T> Flow<ResponseResult<T>>.sendReqWithRetry(
    coroutineScope: CoroutineScope,
    type: Int,
    retryLimit: Int = 0,
    doWork: ((data: T?) -> Unit)? = null){
    coroutineScope.launch(Dispatchers.IO) {
        this@sendReqWithRetry.retryWhen { cause, attempt ->
            retryTimeMap.containsKey(type).and(
                retryLimit <= 0 || attempt <= retryLimit
            ).also{
                if(it){
                    retryTimeMap[type] = attempt.toInt()
                    val retryTime = getDelayTimeByRetryTimes(type)
                    Log.w(TAG, "$type req failed[$cause] will retry " +
                            "$attempt-count in next ${retryTime/1000} s")
                    delay(retryTime)
                }else if(retryLimit > 0){
                    Log.w(TAG, LOG_INFO)
                }
            }
        }.catch {
            Log.w(TAG, "$type req failed: $it")
            emit(ResponseResult.failure(ResponseError("506", error = it)))
        }.collect{
            doWork?.invoke(it.data)
        }
    }
}

/***
 * 发送请求，若存在Exception则包装为ResponseError返回请求失败结果
 * @param name 发起请求函数名（可选，用于日志分析）
 * @param doWork 结果回调函数
 * */
fun <T> Flow<ResponseResult<T>>.sendReq(
    coroutineScope: CoroutineScope,
    name :String = "",
    doWork: ((data: ResponseResult<T>?) -> Unit?)? = null
){
    coroutineScope.launch (Dispatchers.IO){
        this@sendReq.catch {
            Log.w(TAG, "$name failed: $it")
            emit(ResponseResult.failure(
                ResponseError(
                    if(it is ResponseFailedException)  it.code else "506",
                    error = it
                )
            ))
        }.collect {
            doWork?.invoke(it)
        }
    }
}

/**
 * 根据类型计算重试延迟周期,默认周期为DEFAULT_RETRY_TIME=5000ms
 * @param type 请求数据类型
 * @return Long 重试周期(ms)
 * */
private fun getDelayTimeByRetryTimes(type: Int): Long {
    var retryTime = 0
    retryTimeMap[type]?.let {
        retryTime = it
    }
    var delayTime: Long = DEFAULT_RETRY_TIME
    if (retryTime > 50) {
        delayTime = DEFAULT_RETRY_TIME * 6
    } else if (retryTime > 20) {
        delayTime = DEFAULT_RETRY_TIME * 4
    } else if (retryTime > 10) {
        delayTime = DEFAULT_RETRY_TIME * 2
    }
    return delayTime
}